home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 050 / madtrb34.arc / CLONE.PAS < prev    next >
Pascal/Delphi Source File  |  1986-04-06  |  21KB  |  403 lines

  1. (******************************************************************************
  2.  
  3.                                    CLONE.PAS
  4.                                    Version 3
  5.                                October 17, 1985
  6.                             Borland SIG, CompuServe
  7.  
  8.                                by: Bob Tolz        70475,1071
  9.                   with input from: Randy Forgaard  70307,521
  10.                                    Kim Kokkonen    72457,2131
  11.                                    Bela Lubkin     76703,3015
  12.  
  13.  
  14. This Turbo Pascal program demonstrates how to have a program "clone" a copy of
  15. itself as a new .COM file, after the user has entered any desired changes.
  16. This is analogous to the Setup menus of Sidekick and SuperKey, and similar to
  17. the effect that TINST would achieve if it were built right into the Turbo
  18. compiler itself.  It can also be used to clone slightly different versions of a
  19. program, for different purposes.
  20.  
  21. What follows is a rather lengthy description of the implementation of this
  22. program, called CLONE.  However, CLONE.PAS is ready to run as-is.  If you just
  23. want to see what CLONE does, compile CLONE.PAS ==> to a .COM file <== (very
  24. important), under DOS Turbo 2.0 or 3.0, and then run CLONE.COM.  Furthermore,
  25. don't be intimidated by the large size of this file; when you take out the
  26. documentation and the test program, the resulting code only fills a screen or
  27. two.
  28.  
  29. CLONE has been tested using all regular, 8087, and BCD versions of DOS Turbo
  30. Pascal 2.00B, 3.00B, and 3.01A.  (We will use DOS to mean both MS-DOS and
  31. PC-DOS.)  It probably runs without change with all other versions of the DOS
  32. Turbo 2.0 and 3.0 compilers.  If you have a different version of DOS Turbo, and
  33. CLONE works for you, or you have modified CLONE to work for you, please let us
  34. know, or upload a new copy of CLONE.
  35.  
  36. With some coaxing, CLONE could be modified to run under CP/M-86 and CP/M-80.
  37. If you make CLONE work on any non-DOS system, please incorporate the new
  38. procedures required into this file, and upload a new CLONE.  We would love to
  39. see CLONE become as generally applicable as possible.
  40.  
  41. The theory behind CLONE is as follows: CLONE is designed to be run as a
  42. compiled Turbo program.  When loaded into memory, it has the capability to make
  43. a copy of itself, by just writing the image of itself, which already resides in
  44. memory, to a new .COM file.  If the user changes some typed constants in CLONE
  45. before CLONE clones itself, the new copy of clone will reflect those new values
  46. for the typed constants.  Voila!  A program that can "install" itself.  The
  47. trick in implementing CLONE is to find out where the memory image of CLONE.COM
  48. begins, and how long it is, so that CLONE can write out the correct memory
  49. image of itself as a new .COM file.
  50.  
  51. The first question is easy.  When DOS loads CLONE.COM (or any .COM program)
  52. into memory, it first builds a 256 byte ($100 bytes, in hexadecimal) Program
  53. Segment Prefix (PSP) in memory, and then reads CLONE.COM into the memory
  54. immediately following the PSP.  DOS then sets the CS (code segment) register so
  55. that address CS:0000 is the beginning of the PSP.  Consequently, the contents
  56. of CLONE.COM really start at CS:0100, or, in Turbo parlance, CSeg:$0100.
  57.  
  58. The second question, the code length of CLONE.COM in memory, is a little
  59. trickier.  One way to find out is to compile CLONE, issue a "DIR CLONE.COM"
  60. command at the DOS prompt to see how big the .COM file is, and then hard-code
  61. this constant into CLONE.COM.  The disadvantage of this method is that every
  62. time you change CLONE, you have to compile it, see how long it is, change that
  63. constant in the CLONE code, and then re-compile CLONE.
  64.  
  65. We propose an alternate scheme, to have CLONE "discover," at run-time, how many
  66. bytes the memory image of itself occupies.  It turns out that .COM programs
  67. produced by DOS Turbo set up the DS (data segment) register so that the address
  68. DS:0000 is the first 16-byte paragraph boundary past the end of the .COM file
  69. code.  Depending on where the .COM code ends, there can be up to 15 "garbage"
  70. bytes between the end of the .COM code and the beginning of the data segment,
  71. because of the 16-byte paragraph boundary restriction on DS:0000.  Thus, we
  72. could say that the .COM code ends at DSeg:$0000 (in Turbo notation), and that
  73. would be almost correct, but it would usually include a few extra garbage bytes
  74. that weren't actually part of the .COM file.
  75.  
  76. One school of thought is to just keep those few garbage bytes as part of the
  77. cloned .COM file that gets produced by CLONE.  I.e., when CLONE writes out a
  78. "copy" of itself, it could include all the bytes between CS:0100 and DS:0000,
  79. including those few extra garbage bytes.  Then, when you tell DOS to run the
  80. cloned copy of the program, DOS will just load those garbage bytes into memory
  81. along with the rest of the .COM file.  These extra bytes will not affect the
  82. execution of the cloned copy, any more than they affected the execution of
  83. CLONE when they happened to be located in memory when CLONE decided to clone
  84. itself.  However, it may be disconcerting to have the cloned copy of a program
  85. be a few bytes longer than the original, due to the garbage bytes.  In
  86. particular, you won't be able to use the DOS COMP program to compare the
  87. original .COM file with its clone, because the files will be different sizes.
  88.  
  89. However, if you decide that the garbage bytes are all right, in the interest of
  90. removing some complexity from CLONE, all you have to do is run CLONE.COM once
  91. so that it makes a clone of itself (including the garbage bytes).  If you use
  92. the clone of CLONE to make another CLONE, the second CLONE will be exactly the
  93. same size as the first clone of CLONE.  Thus, if you use the clone of CLONE as
  94. your distribution copy, all clones of the distribution copy will be the same
  95. size as the distribution copy, so the DOS COMP program will be able to compare
  96. clones.  In summary, you can choose to use a cloned copy of CLONE, so that all
  97. clones of the cloned CLONE will be the same size as the first clone of CLONE.
  98. (There will be a quiz on this material.)
  99.  
  100. If you decide to use the simpler scheme that includes the few garbage bytes at
  101. the end of the cloned .COM file, you can replace the body of the CodeSize
  102. function, below, with the single statement:
  103.   CodeSize := ((DSeg - CSeg) shl 4) - $100
  104. and omit the "while" loop and the variable declaration entirely.
  105.  
  106. However, suppose you want the clones to be the same size as the originals, and
  107. you don't want to have to perform an extra cloning step prior to each time you
  108. distribute your program.  After some discussion with various DOS and Turbo
  109. wizards, including Borland itself, we have not discovered any magic location in
  110. the Turbo run-time system, or any DOS function call, or any field in the PSP,
  111. that can be used to compute the actual length of the loaded .COM file, minus
  112. the garbage bytes.  However, we have found a reliable way to compute this
  113. length by having CLONE examine the last few machine instructions of itself in
  114. memory.  In particular, we have found that .COM files that were compiled using
  115. either the regular or the 8087 version of Turbo 2.00B always end in the
  116. following pattern of bytes:
  117.  
  118.   E9 00 00 E8 ? ?
  119.  
  120. where ? is any byte at all.  This is probably true of any of the Turbo 2.0
  121. compilers, not just the 2.00B compilers that we tested.  Likewise, .COM files
  122. produced by the regular, 8087, or BCD versions of Turbo 3.00B and 3.01A always
  123. end in the following pattern of bytes:
  124.  
  125.   ? 00 00
  126.  
  127. where ? is any byte except E9.  Again, this pattern probably holds for all
  128. versions of Turbo 3.0, not just the 3.00B and 3.01A compilers we tested.
  129.  
  130. The CodeSize function, below, computes the length of the loaded .COM file at
  131. run-time by examining the bytes immediately prior to DSeg:$0000 to find the
  132. above patterns.  For a pattern that is "n" bytes long, it looks for the first
  133. occurrence of the pattern starting at the memory location that is 16-n+1 bytes
  134. prior to DSeg:$0000.  It turns out that the Turbo 2.0 pattern and Turbo 3.0
  135. pattern are mutually exclusive, so there is no possibility of accidentally
  136. finding the Turbo 2.0 pattern near the end of a Turbo 3.0 .COM file and vice
  137. versa.  Consequently, for simplicity of exposition, CodeSize looks for both the
  138. Turbo 2.0 pattern and the Turbo 3.0 pattern at the same time, since there is no
  139. danger of confusion.  If you know for certain that you will only be using the
  140. Turbo 3.0 compiler, you can remove the test for the Turbo 2.0 pattern from
  141. CodeSize, and vice versa.
  142.  
  143. If you find byte patterns for any other versions of Turbo, or can verify that
  144. the byte patterns used by the CodeSize function below already work for versions
  145. of DOS Turbo other than 2.00B, 3.00B, and 3.01A (regular, 8087, and BCD),
  146. please let us know, and/or upload a new copy of CLONE.PAS that incorporates the
  147. appropriate generalization of the CodeSize function.
  148.  
  149. One way to discover the byte patterns at the end of a .COM file, produced by a
  150. version of the Turbo compiler that we have not yet tried, is to compile several
  151. programs, into .COM files, using that Turbo compiler, and use the DEBUG program
  152. that comes with DOS to look for similarities at the ends of the .COM files.
  153. For example, suppose DIR shows that TEST.COM, produced by your Turbo compiler,
  154. has a length of 12396.  Converting that to hex (which is $306C), and adding
  155. $100 (for the PSP), indicates that memory location CS:316C will be the first
  156. byte past the end of the .COM file.  (It will be the first byte past the end,
  157. rather than the last byte itself, because we start counting from CS:0000
  158. instead of CS:0001.)  CS:316B (one less than CS:316C) will be the actual last
  159. byte of the .COM file.  To find a a pattern, it is prudent to look at least at
  160. the last $20 bytes (2 16-byte paragraphs) of the .COM file.  For this example,
  161. we issue the command:
  162.  
  163.   D314C 316B
  164.  
  165. at the DEBUG "-" prompt.  (The CS segment register is implicitly used by DEBUG
  166. in this command.)  DEBUG will display the bytes in those 32 locations.  Save
  167. the contents of the screen to your printer, or (if you have Sidekick) to a
  168. file, and do a "Q" to quit out of DEBUG.  Using DEBUG and your printer, compare
  169. those bytes to the last 32 bytes of the .COM files produced by your Turbo
  170. compiler for several other example programs.  A pattern should emerge.  Note
  171. that the pattern will almost certainly consist of certain bytes that are always
  172. the same, and certain "?"  bytes that are less restricted.  Once the pattern
  173. has been found, it can be incorporated into CodeSize to extend the number of
  174. Turbo compilers for which CodeSize will work.
  175.  
  176. If you run the DOS COMP program to compare CLONE.COM with a clone of itself,
  177. you may find discrepancies at a few byte locations.  For example, using the
  178. regular 3.00B compiler, we found that the byte at $92 and the word (two bytes)
  179. at $2BD2 differed between CLONE.COM and its clone.  These reflect changes that
  180. the Turbo run-time system makes to itself after CLONE.COM has been loaded by
  181. DOS.  Bela Lubkin of Borland has indicated that $92 is irrelevant, and that
  182. $2BD2/$2BD3 is probably innocuous.  If you do have a problem, or if you want to
  183. make triple sure that there won't be a problem, then: 1) use COMP to see what
  184. data should be in those byte locations, and 2) immediately before the
  185. BlockWrite operation in the Clone procedure below, insert some assignment
  186. statements to the appropriate Mem and MemW locations, e.g.:
  187.            Mem[CSeg:$0092]  := ????
  188.            MemW[CSeg:$2CD2] := ????
  189.  
  190. The CodeSize function is only half of the story.  The other half is the Clone
  191. procedure itself.  The Clone procedure uses the CodeSize function to determine
  192. how long the new .COM file will be, and then writes those bytes directly from
  193. memory to a new .COM file.  Using Turbo 3.0 for DOS, this is very easy to do,
  194. using BlockWrite and the new optional record size parameter for Rewrite,
  195. allowing us to declare the record size to be one byte long.  This allows us to
  196. write a new .COM file that is exactly the right length.
  197.  
  198. Using Turbo 2.0, or a non-DOS version of Turbo 3.0, is more difficult.  In
  199. these cases, BlockWrite can only be used to write 128-byte blocks, to the
  200. resulting .COM file will not be the exact byte length we are looking for.  One
  201. way around this is to use a File of Byte, and to write the new .COM file one
  202. byte at a time, but this turns out to be dreadfully slow.  Instead, we have
  203. opted below for a DOS-specific version of Clone that uses DOS function calls
  204. (assumes DOS 2.0 or higher) to write the new .COM file very quickly.
  205.  
  206. We have written two versions of the Clone procedure below.  The first one,
  207. involving DOS function calls, will work under both Turbo 2.0 and 3.0.  The
  208. second version of Clone, which is much cleaner than the first and runs just as
  209. fast, can only be compiled under DOS Turbo 3.0.  As this CLONE.PAS file
  210. currently exists, the first Clone procedure will be used, due to a sneaky
  211. commenting convention.  However, as indicated in the comment immediately
  212. preceding the first Clone procedure, deleting a single character from this
  213. source file will cause the second, more compact Clone procedure to be used
  214. instead.
  215.  
  216. A caution: The CLONE technique has not been tried with any programs that use
  217. overlays.  This could be quite tricky, because the memory image of CLONE would
  218. change as soon as program execution caused an overlay to be read from disk.
  219. The CLONE idea might be usable with overlayed programs if no overlays get
  220. loaded prior to cloning, but this has not been tested.  Please let us know if
  221. you come up with any results or insights in this regard.
  222.  
  223. CP/M-86 users: We would be very interested if someone could add, to this
  224. CLONE.PAS file, a special version of the Clone procedure, and an enhancement or
  225. new version of the CodeSize function, that will work for CP/M-86.  A CodeSize
  226. function will probably still be required if one wishes to avoid cloning the
  227. garbage bytes between the end of the .CMD file image and the beginning of the
  228. data segment.  The byte patterns to look for will probably be different for
  229. CP/M-86 than for DOS, so additional clauses will probably have to be added to
  230. the "while" loop in the CodeSize function.  The subtraction of $100, in
  231. CodeSize, should be omitted, since CP/M-86 sets the CS segment register such
  232. that CS:0000 (rather than CS:0100) is the beginning of the loaded program.
  233. Also, the Clone procedure will have to be rewritten, either to use a File of
  234. Byte (slow), or to replace the DOS function calls with CP/M-86 function calls.
  235.  
  236. CP/M-80 users: It would be terrific if someone could CP/M-80 capability to this
  237. CLONE.PAS file.  It appears, from reading the Turbo manual, that the heap
  238. (rather than the data segment) starts immediately after the object code of the
  239. loaded .COM file, with no garbage bytes in-between.  Thus, the length of the
  240. .COM file can probably be determined by subtracting, from the initial value of
  241. HeapPtr, the size of CP/M and its run-time workspace.  It is pretty clear, in
  242. any case, that the CodeSize function for CP/M-80 would be very different, and
  243. probably much simpler.  The Clone procedure will have to be rewritten also, so
  244. as not to use DOS function calls or features that are specific to the DOS
  245. version of Turbo.
  246.  
  247. We hereby donate CLONE to the Public Domain...happy cloning!
  248.  
  249. ******************************************************************************)
  250.  
  251.  
  252. program CloneDemo;
  253.  
  254. type
  255.   FileName = string[80];
  256.   Message =  string[80];
  257.  
  258.  
  259. {This function returns the number of bytes occupied by the image of this .COM
  260.  file in memory.  Known to work for Turbo programs compiled under the regular,
  261.  8087, and BCD versions of the Turbo 2.00B, 3.00B, and 3.01A compilers for DOS.
  262.  See comments above for details.}
  263.  
  264. function CodeSize: Integer;
  265. var
  266.   i: Byte;
  267. begin
  268.   i := 11;
  269.   while   {Turbo version is marked on the left:}
  270.    {3.0:} not ((Mem [DSeg-2:i+3] <> $00E9) and (MemW[DSeg-2:i+4] = $0000)) and
  271.    {2.0:} not ((MemW[DSeg-2:i+0] =  $00E9) and (MemW[DSeg-2:i+2] = $E800)) do
  272.      i := i + 1;
  273.   CodeSize := ((((DSeg - 2) - CSeg) shl 4) + i + 6) - $100
  274. end {CodeSize};
  275.  
  276.  
  277. (*Currently, the first Clone procedure below will be compiled.  To use the
  278.   second Clone procedure, delete the curly bracket on the line immediately
  279.   following this comment.*)
  280. {
  281.  
  282. (*The next line is part of the Clone selection scheme.*)
  283. (* }
  284.  
  285.  
  286. {Writes, into the file "fn," a clone of this program, including any new values
  287.  for typed constants that may have been changed since the program was loaded.
  288.  This is the long version of Clone that uses DOS function calls (assumes DOS
  289.  2.0 or higher) to quickly write out the new .COM file.  It can be used both
  290.  with the 2.0 and the 3.0 versions of Turbo for DOS.  See comments above for
  291.  details.}
  292.  
  293. procedure Clone (fn: FileName);
  294.  
  295.   procedure Abort(msg: Message);
  296.   begin
  297.     writeln(msg);
  298.     Halt
  299.   end {Abort};
  300.  
  301. var
  302.   handle, length: Integer;
  303.   regPack: record
  304.              case Integer of
  305.                1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
  306.                2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte)
  307.            end;
  308.   writeError: Boolean;
  309. begin
  310.   with regPack do
  311.     begin
  312.       fn := fn + #0;             {Convert "fn" to an ASCIIZ string}
  313.       length := CodeSize;        {Length of code image in memory}
  314.       AH := $3C;                 {Create a file}
  315.       DS := Seg(fn[1]);          {Segment of ASCIIZ file name}
  316.       DX := Ofs(fn[1]);          {Offset of ASCIIZ file name}
  317.       CX := 0;                   {Default attributes}
  318.       MsDos(regPack);            {Create the clone file}
  319.       if Odd(Flags) then         {Check if carry bit is set}
  320.         Abort('Unable to create file');
  321.       handle := regPack.AX;      {Retrieve handle for opened file}
  322.       AH := $40;                 {Write to a file}
  323.       BX := handle;              {File to write to}
  324.       DS := CSeg;                {Segment of code}
  325.       DX := $100;                {Beginning address of code}
  326.       CX := length;              {Length of code}
  327.       MsDos(regPack);            {Write the code to the clone file}
  328.       writeError := Odd(Flags) or (AX <> length);
  329.       if writeError then         {Allow the file to be closed, anyway}
  330.         writeln('Unable to write to file');
  331.       AH := $3E;                 {Close a file}
  332.       BX := handle;              {File to close}
  333.       MsDos(regPack);            {Close the output file}
  334.       if Odd(Flags) then         {Check if carry bit is set}
  335.         Abort('Unable to close file');
  336.       if writeError then Halt    {Halt if there was a write error previously}
  337.     end
  338. end {Clone};
  339.  
  340.  
  341. {The next line is part of the Clone selection scheme.}
  342. { *)
  343.  
  344.  
  345. (*Writes, into the file "fn," a clone of this program, including any new values
  346.   for typed constants that may have been changed since the program was loaded.
  347.   This is the more elegant version of CLONE that uses BlockWrite and the
  348.   optional record size parameter to Rewrite to quickly write out the new .COM
  349.   file.  It can only be used with DOS Turbo 3.0 and higher.  See comments above
  350.   for details.*)
  351.  
  352. procedure Clone (fn: FileName);
  353. var
  354.   f: File;
  355.   contents: Byte Absolute CSeg:$0100;
  356. begin
  357.   Assign(f, fn);
  358.   Rewrite(f, 1);
  359.   BlockWrite(f, contents, CodeSize);
  360.   Close(f)
  361. end (*Clone*);
  362.  
  363.  
  364. (*The next line is part of the Clone selection scheme.*)
  365. { }
  366.  
  367.  
  368. {Test program for CLONE:}
  369.  
  370. const
  371.   default: Message =
  372.     '"How many Fortune 1000 companies are there, anyway?"  -- Steve Jobs';
  373. var
  374.   newFile: FileName;
  375.   new: Message;
  376. begin
  377.   writeln('The size of this .COM file is ', CodeSize, ' bytes.');
  378.   writeln('The program currently contains the following default string:');
  379.   writeln;
  380.   writeln('=> ', default);
  381.   writeln;
  382.   writeln('Please enter a new value for this default string, or just press ');
  383.   writeln('Enter (Return) if you do not want to change the default value: ');
  384.   writeln;
  385.   write('=> ');
  386.   readln(new);
  387.   writeln;
  388.   if Length(new) > 0 then default := new;
  389.   writeln('Please enter a file name (ending in .COM) for storing the clone, ');
  390.   writeln('which can be the same name as the original .COM file, or just ');
  391.   writeln('press Enter (Return) if you do not want to make a clone: ');
  392.   write('=> ');
  393.   readln(newFile);
  394.   if Length(newFile) > 0 then
  395.     begin
  396.       Clone(newFile);
  397.       writeln;
  398.       writeln('If you now run "', newFile,
  399.               '," you will see the new default string.');
  400.       writeln('This program has just changed its own defaults!')
  401.     end
  402. end {CloneDemo}.
  403.